package com.xiaolei.fastnio.http

import com.xiaolei.fastnio.http.Comm.ContentType
import com.xiaolei.fastnio.http.Comm.HttpStatus
import com.xiaolei.fastnio.http.Ext.elseOr
import com.xiaolei.fastnio.http.Ext.ifTrue
import com.xiaolei.fastnio.library.Client.Session
import com.xiaolei.fastnio.library.Protocol.Cleanable
import java.io.File
import java.io.InputStream
import java.lang.StringBuilder

/**
 * 响应类
 */
class Response : Cleanable
{
    var session: Session? = null
        set(value)
        {
            status = HttpStatus.OK
            message = "OK"
            field = value
        }

    /**
     * 响应的状态码
     */
    var status = HttpStatus.OK

    /**
     * 状态码原因短句
     */
    var message = "OK"

    /**
     * 响应头
     */
    val headers = HashMap<String, String>()

    /**
     * Cookies
     */
    val cookies = HashMap<String, String>()

    /**
     * 响应体
     */
    var body: ResponseBody? = null

    /**
     * 头部字符串组合
     */
    private val headerBuilder = StringBuilder()

    /**
     * 添加一个cookie
     */
    fun addCookie(key: String, value: String)
    {
        this.cookies[key] = value
    }

    /**
     * 覆盖式的设置cookie
     */
    fun setCookie(cookies: Map<String, String>)
    {
        this.cookies.clear()
        this.cookies.putAll(cookies)
    }

    /**
     * 设置头
     */
    fun setHeader(key: String, value: String)
    {
        headers[key] = value
    }

    /**
     * 请求转发
     */
    fun sendRedirect(uri: String)
    {
        this.status = HttpStatus.REDIRECT
        setHeader("Location", uri ifTrue { it.startsWith("/") } elseOr "/$uri")
    }

    /**
     * 往外写出Html内容
     */
    fun writeHtml(html: String)
    {
        body = ResponseBody(ResponseBodyType.HTML, html)
    }

    /**
     * 往外写出JSON数据
     */
    fun writeJson(json: String)
    {
        body = ResponseBody(ResponseBodyType.JSON, json)
    }

    /**
     * 往外写文件/下载文件
     * @param file 文件
     */
    fun writeDownload(file: File)
    {
        setHeader("Content-disposition", "attachment;filename=" + file.name)
        writeDownload(file.inputStream())
    }

    /**
     * 往外写文件/下载文件
     * @param stream 数据流
     */
    fun writeDownload(stream: InputStream)
    {
        body = ResponseBody(ResponseBodyType.DownLoad, stream)
    }

    /**
     * 往外写二进制数据
     * @param file 文件
     */
    fun writeStream(file: File)
    {
        setHeader("Content-Type", ContentType.getContentType(file.extension))
        writeStream(file.inputStream())
    }

    /**
     * 往外写二进制数据
     * @param stream 数据流
     */
    fun writeStream(stream: InputStream)
    {
        body = ResponseBody(ResponseBodyType.Stream, stream)
    }

    /**
     * 根据保存的信息，初始化header数据
     */
    private inline fun buildHeader(): StringBuilder
    {
        headerBuilder.setLength(0)
        headerBuilder.append("HTTP/1.1 ").append(status.code).append(" ").append(message).append("\r\n")
        for ((key, value) in headers)
        {
            headerBuilder.append(key).append(": ").append(value).append("\r\n")
        }
        if (cookies.isNotEmpty())
        {
            for ((k, v) in cookies)
            {
                headerBuilder.append("Set-Cookie").append(": ")
                        .append(k).append("=").append(v)
                        .append("\r\n")
            }
        }
        headerBuilder.append("\r\n")
        return headerBuilder
    }

    /**
     * 刷新缓存
     */
    fun flush() = session?.let { session ->
        writeHeader(session)
        writeBody(session)
    }

    /**
     * 写头
     */
    private fun writeHeader(session: Session)
    {
        when (body)
        {
            null -> Unit
            else -> when
            {
                headers["Content-Type"] != null -> Unit
                else -> headers["Content-Type"] = when (body?.type)
                {
                    ResponseBodyType.HTML -> ContentType.html
                    ResponseBodyType.JSON -> ContentType.json
                    ResponseBodyType.DownLoad ->
                    {
                        val stream = body?.any as? InputStream
                        stream?.available()?.let { length ->
                            headers["Content-Length"] = "$length"
                        }
                        ContentType.octStream
                    }
                    ResponseBodyType.Stream ->
                    {
                        val stream = body?.any as? InputStream
                        stream?.available()?.let { length ->
                            headers["Content-Length"] = "$length"
                        }
                        ContentType.default
                    }
                    else -> ContentType.default
                }
            }
        }
        val headers = buildHeader().toString()
        session.write(headers.toByteArray())
    }

    /**
     * 写体
     */
    private fun writeBody(session: Session)
    {
        when (body)
        {
            null -> Unit
            else -> when (body?.type)
            {
                ResponseBodyType.JSON,
                ResponseBodyType.HTML ->
                {
                    session.write(body!!.any.toString().toByteArray())
                }
                ResponseBodyType.DownLoad, ResponseBodyType.Stream ->
                {
                    val inputStream = body?.any as? InputStream ?: return
                    val buff = ByteArray(1024)
                    var len = inputStream.read(buff)
                    while (len > 0)
                    {
                        session.write(buff, 0, len)
                        len = inputStream.read(buff)
                    }
                    inputStream.close()
                }
                else -> Unit
            }
        }
    }


    /**
     * 关闭会话
     */
    fun close() = session?.close()

    /**
     * 清理资源
     */
    override fun onClear()
    {
        headerBuilder.setLength(0)
        body = null
        session = null
        headers.clear()
        cookies.clear()
    }

    /**
     * 响应体
     */
    inner class ResponseBody(val type: ResponseBodyType, val any: Any)

    /**
     * 响应体类型
     */
    enum class ResponseBodyType(desc: String)
    {
        HTML("普通网页"),
        JSON("JSON字符串"),
        DownLoad("下载文件"),
        Stream("作为资源输出"),
    }
}